1   /*
2    * Copyright (C) 2008 The Android Open Source Project
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.android.launcher;
18  
19  import android.content.ComponentName;
20  import android.content.ContentResolver;
21  import android.content.ContentValues;
22  import android.content.Intent;
23  import android.content.Context;
24  import android.content.pm.ActivityInfo;
25  import android.content.pm.PackageManager;
26  import android.content.pm.ResolveInfo;
27  import android.content.res.Resources;
28  import android.database.Cursor;
29  import android.graphics.Bitmap;
30  import android.graphics.BitmapFactory;
31  import android.graphics.drawable.Drawable;
32  import android.net.Uri;
33  import static android.util.Log.*;
34  import android.os.Process;
35  
36  import java.util.ArrayList;
37  import java.util.HashMap;
38  import java.util.List;
39  import java.util.Comparator;
40  import java.util.concurrent.atomic.AtomicInteger;
41  import java.lang.ref.WeakReference;
42  import java.text.Collator;
43  import java.net.URISyntaxException;
44  
45  /**
46   * Maintains in-memory state of the Launcher. It is expected that there should be only one
47   * LauncherModel object held in a static. Also provide APIs for updating the database state
48   * for the Launcher.
49   */
50  public class LauncherModel {
51      static final boolean DEBUG_LOADERS = false;
52      static final String LOG_TAG = "HomeLoaders";
53  
54      private static final int UI_NOTIFICATION_RATE = 4;
55      private static final int DEFAULT_APPLICATIONS_NUMBER = 42;
56      private static final long APPLICATION_NOT_RESPONDING_TIMEOUT = 5000;
57      private static final int INITIAL_ICON_CACHE_CAPACITY = 50;
58  
59      private static final Collator sCollator = Collator.getInstance();
60  
61      private boolean mApplicationsLoaded;
62      private boolean mDesktopItemsLoaded;
63  
64      private ArrayList<ItemInfo> mDesktopItems;
65      private ArrayList<LauncherAppWidgetInfo> mDesktopAppWidgets;
66      private HashMap<Long, FolderInfo> mFolders;
67  
68      private ArrayList<ApplicationInfo> mApplications;
69      private ApplicationsAdapter mApplicationsAdapter;
70      private ApplicationsLoader mApplicationsLoader;
71      private DesktopItemsLoader mDesktopItemsLoader;
72      private Thread mApplicationsLoaderThread;
73      private Thread mDesktopLoaderThread;
74  
75      private final HashMap<ComponentName, ApplicationInfo> mAppInfoCache =
76              new HashMap<ComponentName, ApplicationInfo>(INITIAL_ICON_CACHE_CAPACITY);
77  
78      synchronized void abortLoaders() {
79          if (DEBUG_LOADERS) d(LOG_TAG, "aborting loaders");
80  
81          if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) {
82              if (DEBUG_LOADERS) d(LOG_TAG, "  --> aborting applications loader");
83              mApplicationsLoader.stop();
84              mApplicationsLoaded = false;
85          }
86  
87          if (mDesktopItemsLoader != null && mDesktopItemsLoader.isRunning()) {
88              if (DEBUG_LOADERS) d(LOG_TAG, "  --> aborting workspace loader");
89              mDesktopItemsLoader.stop();
90              mDesktopItemsLoaded = false;
91          }
92      }
93  
94      /**
95       * Drop our cache of components to their lables & icons.  We do
96       * this from Launcher when applications are added/removed.  It's a
97       * bit overkill, but it's a rare operation anyway.
98       */
99      synchronized void dropApplicationCache() {
100         mAppInfoCache.clear();
101     }
102 
103     /**
104      * Loads the list of installed applications in mApplications.
105      *
106      * @return true if the applications loader must be started
107      *         (see startApplicationsLoader()), false otherwise.
108      */
109     synchronized boolean loadApplications(boolean isLaunching, Launcher launcher,
110             boolean localeChanged) {
111 
112         if (DEBUG_LOADERS) d(LOG_TAG, "load applications");
113 
114         if (isLaunching && mApplicationsLoaded && !localeChanged) {
115             mApplicationsAdapter = new ApplicationsAdapter(launcher, mApplications);
116             if (DEBUG_LOADERS) d(LOG_TAG, "  --> applications loaded, return");
117             return false;
118         }
119 
120         stopAndWaitForApplicationsLoader();
121 
122         if (localeChanged) {
123             dropApplicationCache();
124         }
125 
126         if (mApplicationsAdapter == null || isLaunching || localeChanged) {
127             mApplications = new ArrayList<ApplicationInfo>(DEFAULT_APPLICATIONS_NUMBER);
128             mApplicationsAdapter = new ApplicationsAdapter(launcher, mApplications);
129         }
130 
131         mApplicationsLoaded = false;
132 
133         if (!isLaunching) {
134             startApplicationsLoaderLocked(launcher, false);
135             return false;
136         }
137 
138         return true;
139     }
140 
141     private synchronized void stopAndWaitForApplicationsLoader() {
142         if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) {
143             if (DEBUG_LOADERS) {
144                 d(LOG_TAG, "  --> wait for applications loader (" + mApplicationsLoader.mId + ")");
145             }
146 
147             mApplicationsLoader.stop();
148             // Wait for the currently running thread to finish, this can take a little
149             // time but it should be well below the timeout limit
150             try {
151                 mApplicationsLoaderThread.join(APPLICATION_NOT_RESPONDING_TIMEOUT);
152             } catch (InterruptedException e) {
153                 // Empty
154             }
155         }
156     }
157 
158     private synchronized void startApplicationsLoader(Launcher launcher, boolean isLaunching) {
159         if (DEBUG_LOADERS) d(LOG_TAG, "  --> starting applications loader unlocked");
160         startApplicationsLoaderLocked(launcher, isLaunching);
161     }
162 
163     private void startApplicationsLoaderLocked(Launcher launcher, boolean isLaunching) {
164         if (DEBUG_LOADERS) d(LOG_TAG, "  --> starting applications loader");
165 
166         stopAndWaitForApplicationsLoader();
167 
168         mApplicationsLoader = new ApplicationsLoader(launcher, isLaunching);
169         mApplicationsLoaderThread = new Thread(mApplicationsLoader, "Applications Loader");
170         mApplicationsLoaderThread.start();
171     }
172 
173     synchronized void addPackage(Launcher launcher, String packageName) {
174         if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) {
175             startApplicationsLoaderLocked(launcher, false);
176             return;
177         }
178 
179         if (packageName != null && packageName.length() > 0) {
180             final PackageManager packageManager = launcher.getPackageManager();
181             final List<ResolveInfo> matches = findActivitiesForPackage(packageManager, packageName);
182 
183             if (matches.size() > 0) {
184                 final ApplicationsAdapter adapter = mApplicationsAdapter;
185                 final HashMap<ComponentName, ApplicationInfo> cache = mAppInfoCache;
186 
187                 for (ResolveInfo info : matches) {
188                     adapter.setNotifyOnChange(false);
189                     adapter.add(makeAndCacheApplicationInfo(packageManager, cache, info, launcher));
190                 }
191 
192                 adapter.sort(new ApplicationInfoComparator());
193                 adapter.notifyDataSetChanged();
194             }
195         }
196     }
197 
198     synchronized void removePackage(Launcher launcher, String packageName) {
199         if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) {
200             dropApplicationCache(); // TODO: this could be optimized
201             startApplicationsLoaderLocked(launcher, false);
202             return;
203         }
204 
205         if (packageName != null && packageName.length() > 0) {
206             final ApplicationsAdapter adapter = mApplicationsAdapter;
207 
208             final List<ApplicationInfo> toRemove = new ArrayList<ApplicationInfo>();
209             final int count = adapter.getCount();
210 
211             for (int i = 0; i < count; i++) {
212                 final ApplicationInfo applicationInfo = adapter.getItem(i);
213                 final Intent intent = applicationInfo.intent;
214                 final ComponentName component = intent.getComponent();
215                 if (packageName.equals(component.getPackageName())) {
216                     toRemove.add(applicationInfo);
217                 }
218             }
219 
220             final HashMap<ComponentName, ApplicationInfo> cache = mAppInfoCache;
221             for (ApplicationInfo info : toRemove) {
222                 adapter.setNotifyOnChange(false);
223                 adapter.remove(info);
224                 cache.remove(info.intent.getComponent());
225             }
226 
227             if (toRemove.size() > 0) {
228                 adapter.sort(new ApplicationInfoComparator());
229                 adapter.notifyDataSetChanged();
230             }
231         }
232     }
233 
234     synchronized void updatePackage(Launcher launcher, String packageName) {
235         if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) {
236             startApplicationsLoaderLocked(launcher, false);
237             return;
238         }
239 
240         if (packageName != null && packageName.length() > 0) {
241             final PackageManager packageManager = launcher.getPackageManager();
242             final ApplicationsAdapter adapter = mApplicationsAdapter;
243 
244             final List<ResolveInfo> matches = findActivitiesForPackage(packageManager, packageName);
245             final int count = matches.size();
246 
247             boolean changed = false;
248 
249             for (int i = 0; i < count; i++) {
250                 final ResolveInfo info = matches.get(i);
251                 final ApplicationInfo applicationInfo = findIntent(adapter,
252                         info.activityInfo.applicationInfo.packageName, info.activityInfo.name);
253                 if (applicationInfo != null) {
254                     updateAndCacheApplicationInfo(packageManager, info, applicationInfo, launcher);
255                     changed = true;
256                 }
257             }
258 
259             if (syncLocked(launcher, packageName)) changed = true;
260 
261             if (changed) {
262                 adapter.sort(new ApplicationInfoComparator());
263                 adapter.notifyDataSetChanged();
264             }
265         }
266     }
267 
268     private void updateAndCacheApplicationInfo(PackageManager packageManager, ResolveInfo info,
269             ApplicationInfo applicationInfo, Context context) {
270 
271         updateApplicationInfoTitleAndIcon(packageManager, info, applicationInfo, context);
272 
273         ComponentName componentName = new ComponentName(
274                 info.activityInfo.applicationInfo.packageName, info.activityInfo.name);
275         mAppInfoCache.put(componentName, applicationInfo);
276     }
277 
278     synchronized void syncPackage(Launcher launcher, String packageName) {
279         if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) {
280             startApplicationsLoaderLocked(launcher, false);
281             return;
282         }
283 
284         if (packageName != null && packageName.length() > 0) {
285             if (syncLocked(launcher, packageName)) {
286                 final ApplicationsAdapter adapter = mApplicationsAdapter;
287                 adapter.sort(new ApplicationInfoComparator());
288                 adapter.notifyDataSetChanged();                
289             }
290         }
291     }
292 
293     private boolean syncLocked(Launcher launcher, String packageName) {
294         final PackageManager packageManager = launcher.getPackageManager();
295         final List<ResolveInfo> matches = findActivitiesForPackage(packageManager, packageName);
296 
297         if (matches.size() > 0) {
298             final ApplicationsAdapter adapter = mApplicationsAdapter;
299 
300             // Find disabled activities and remove them from the adapter
301             boolean removed = removeDisabledActivities(packageName, matches, adapter);
302             // Find enable activities and add them to the adapter
303             // Also updates existing activities with new labels/icons
304             boolean added = addEnabledAndUpdateActivities(matches, adapter, launcher);
305 
306             return added || removed;
307         }
308 
309         return false;
310     }
311 
312     private static List<ResolveInfo> findActivitiesForPackage(PackageManager packageManager,
313             String packageName) {
314 
315         final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
316         mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
317 
318         final List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
319         final List<ResolveInfo> matches = new ArrayList<ResolveInfo>();
320 
321         if (apps != null) {
322             // Find all activities that match the packageName
323             int count = apps.size();
324             for (int i = 0; i < count; i++) {
325                 final ResolveInfo info = apps.get(i);
326                 final ActivityInfo activityInfo = info.activityInfo;
327                 if (packageName.equals(activityInfo.packageName)) {
328                     matches.add(info);
329                 }
330             }
331         }
332 
333         return matches;
334     }
335 
336     private boolean addEnabledAndUpdateActivities(List<ResolveInfo> matches,
337             ApplicationsAdapter adapter, Launcher launcher) {
338 
339         final List<ApplicationInfo> toAdd = new ArrayList<ApplicationInfo>();
340         final int count = matches.size();
341 
342         boolean changed = false;
343 
344         for (int i = 0; i < count; i++) {
345             final ResolveInfo info = matches.get(i);
346             final ApplicationInfo applicationInfo = findIntent(adapter,
347                     info.activityInfo.applicationInfo.packageName, info.activityInfo.name);
348             if (applicationInfo == null) {
349                 toAdd.add(makeAndCacheApplicationInfo(launcher.getPackageManager(),
350                         mAppInfoCache, info, launcher));
351                 changed = true;
352             } else {
353                 updateAndCacheApplicationInfo(
354                         launcher.getPackageManager(), info, applicationInfo, launcher);
355                 changed = true;
356             }
357         }
358 
359         for (ApplicationInfo info : toAdd) {
360             adapter.setNotifyOnChange(false);
361             adapter.add(info);
362         }
363 
364         return changed;
365     }
366 
367     private boolean removeDisabledActivities(String packageName, List<ResolveInfo> matches,
368             ApplicationsAdapter adapter) {
369 
370         final List<ApplicationInfo> toRemove = new ArrayList<ApplicationInfo>();
371         final int count = adapter.getCount();
372 
373         boolean changed = false;
374 
375         for (int i = 0; i < count; i++) {
376             final ApplicationInfo applicationInfo = adapter.getItem(i);
377             final Intent intent = applicationInfo.intent;
378             final ComponentName component = intent.getComponent();
379             if (packageName.equals(component.getPackageName())) {
380                 if (!findIntent(matches, component)) {
381                     toRemove.add(applicationInfo);
382                     changed = true;
383                 }
384             }
385         }
386 
387         final HashMap<ComponentName, ApplicationInfo> cache = mAppInfoCache;
388         for (ApplicationInfo info : toRemove) {
389             adapter.setNotifyOnChange(false);
390             adapter.remove(info);
391             cache.remove(info.intent.getComponent());
392         }
393 
394         return changed;
395     }
396 
397     private static ApplicationInfo findIntent(ApplicationsAdapter adapter, String packageName,
398             String name) {
399 
400         final int count = adapter.getCount();
401         for (int i = 0; i < count; i++) {
402             final ApplicationInfo applicationInfo = adapter.getItem(i);
403             final Intent intent = applicationInfo.intent;
404             final ComponentName component = intent.getComponent();
405             if (packageName.equals(component.getPackageName()) &&
406                     name.equals(component.getClassName())) {
407                 return applicationInfo;
408             }
409         }
410 
411         return null;
412     }
413 
414     private static boolean findIntent(List<ResolveInfo> apps, ComponentName component) {
415         final String className = component.getClassName();
416         for (ResolveInfo info : apps) {
417             final ActivityInfo activityInfo = info.activityInfo;
418             if (activityInfo.name.equals(className)) {
419                 return true;
420             }
421         }
422         return false;
423     }
424 
425     Drawable getApplicationInfoIcon(PackageManager manager, ApplicationInfo info) {
426         final ResolveInfo resolveInfo = manager.resolveActivity(info.intent, 0);
427         if (resolveInfo == null) {
428             return null;
429         }
430 
431         ComponentName componentName = new ComponentName(
432                 resolveInfo.activityInfo.applicationInfo.packageName,
433                 resolveInfo.activityInfo.name);
434         ApplicationInfo application = mAppInfoCache.get(componentName);
435 
436         if (application == null) {
437             return resolveInfo.activityInfo.loadIcon(manager);
438         }
439 
440         return application.icon;
441     }
442 
443     private static ApplicationInfo makeAndCacheApplicationInfo(PackageManager manager,
444             HashMap<ComponentName, ApplicationInfo> appInfoCache, ResolveInfo info,
445             Context context) {
446 
447         ComponentName componentName = new ComponentName(
448                 info.activityInfo.applicationInfo.packageName,
449                 info.activityInfo.name);
450         ApplicationInfo application = appInfoCache.get(componentName);
451 
452         if (application == null) {
453             application = new ApplicationInfo();
454             application.container = ItemInfo.NO_ID;
455 
456             updateApplicationInfoTitleAndIcon(manager, info, application, context);
457 
458             application.setActivity(componentName,
459                     Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
460 
461             appInfoCache.put(componentName, application);
462         }
463 
464         return application;
465     }
466 
467     private static void updateApplicationInfoTitleAndIcon(PackageManager manager, ResolveInfo info,
468             ApplicationInfo application, Context context) {
469 
470         application.title = info.loadLabel(manager);
471         if (application.title == null) {
472             application.title = info.activityInfo.name;
473         }
474 
475         application.icon =
476                 Utilities.createIconThumbnail(info.activityInfo.loadIcon(manager), context);
477         application.filtered = false;
478     }
479  
480     private static final AtomicInteger sAppsLoaderCount = new AtomicInteger(1);
481     private static final AtomicInteger sWorkspaceLoaderCount = new AtomicInteger(1);
482 
483     private class ApplicationsLoader implements Runnable {
484         private final WeakReference<Launcher> mLauncher;
485 
486         private volatile boolean mStopped;
487         private volatile boolean mRunning;
488         private final boolean mIsLaunching;
489         private final int mId;
490 
491         ApplicationsLoader(Launcher launcher, boolean isLaunching) {
492             mIsLaunching = isLaunching;
493             mLauncher = new WeakReference<Launcher>(launcher);
494             mRunning = true;
495             mId = sAppsLoaderCount.getAndIncrement();
496         }
497 
498         void stop() {
499             mStopped = true;
500         }
501 
502         boolean isRunning() {
503             return mRunning;
504         }
505 
506         public void run() {
507             if (DEBUG_LOADERS) d(LOG_TAG, "  ----> running applications loader (" + mId + ")");
508 
509             // Elevate priority when Home launches for the first time to avoid
510             // starving at boot time. Staring at a blank home is not cool.
511             android.os.Process.setThreadPriority(mIsLaunching ? Process.THREAD_PRIORITY_DEFAULT :
512                     Process.THREAD_PRIORITY_BACKGROUND);
513 
514             final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
515             mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
516 
517             final Launcher launcher = mLauncher.get();
518             final PackageManager manager = launcher.getPackageManager();
519             final List<ResolveInfo> apps = manager.queryIntentActivities(mainIntent, 0);
520 
521             if (apps != null && !mStopped) {
522                 final int count = apps.size();
523                 // Can be set to null on the UI thread by the unbind() method
524                 // Do not access without checking for null first
525                 final ApplicationsAdapter applicationList = mApplicationsAdapter;
526 
527                 ChangeNotifier action = new ChangeNotifier(applicationList, true);
528                 final HashMap<ComponentName, ApplicationInfo> appInfoCache = mAppInfoCache;
529 
530                 for (int i = 0; i < count && !mStopped; i++) {
531                     ResolveInfo info = apps.get(i);
532                     ApplicationInfo application =
533                         makeAndCacheApplicationInfo(manager, appInfoCache, info, launcher);
534 
535                     if (action.add(application) && !mStopped) {
536                         launcher.runOnUiThread(action);
537                         action = new ChangeNotifier(applicationList, false);
538                     }
539                 }
540 
541                 launcher.runOnUiThread(action);
542             }
543 
544             if (!mStopped) {
545                 mApplicationsLoaded = true;
546             } else {
547                 if (DEBUG_LOADERS) d(LOG_TAG, "  ----> applications loader stopped (" + mId + ")");                                
548             }
549             mRunning = false;
550         }
551     }
552 
553     private static class ChangeNotifier implements Runnable {
554         private final ApplicationsAdapter mApplicationList;
555         private final ArrayList<ApplicationInfo> mBuffer;
556 
557         private boolean mFirst = true;
558 
559         ChangeNotifier(ApplicationsAdapter applicationList, boolean first) {
560             mApplicationList = applicationList;
561             mFirst = first;
562             mBuffer = new ArrayList<ApplicationInfo>(UI_NOTIFICATION_RATE);
563         }
564 
565         public void run() {
566             final ApplicationsAdapter applicationList = mApplicationList;
567             // Can be set to null on the UI thread by the unbind() method
568             if (applicationList == null) return;
569 
570             if (mFirst) {
571                 applicationList.setNotifyOnChange(false);
572                 applicationList.clear();
573                 if (DEBUG_LOADERS) d(LOG_TAG, "  ----> cleared application list");
574                 mFirst = false;
575             }
576 
577             final ArrayList<ApplicationInfo> buffer = mBuffer;
578             final int count = buffer.size();
579 
580             for (int i = 0; i < count; i++) {
581                 applicationList.setNotifyOnChange(false);
582                 applicationList.add(buffer.get(i));
583             }
584 
585             buffer.clear();
586 
587             applicationList.sort(new ApplicationInfoComparator());
588             applicationList.notifyDataSetChanged();
589         }
590 
591         boolean add(ApplicationInfo application) {
592             final ArrayList<ApplicationInfo> buffer = mBuffer;
593             buffer.add(application);
594             return buffer.size() >= UI_NOTIFICATION_RATE;
595         }
596     }
597 
598     static class ApplicationInfoComparator implements Comparator<ApplicationInfo> {
599         public final int compare(ApplicationInfo a, ApplicationInfo b) {
600             return sCollator.compare(a.title.toString(), b.title.toString());
601         }
602     }
603 
604     boolean isDesktopLoaded() {
605         return mDesktopItems != null && mDesktopAppWidgets != null && mDesktopItemsLoaded;
606     }
607 
608     /**
609      * Loads all of the items on the desktop, in folders, or in the dock.
610      * These can be apps, shortcuts or widgets
611      */
612     void loadUserItems(boolean isLaunching, Launcher launcher, boolean localeChanged,
613             boolean loadApplications) {
614         if (DEBUG_LOADERS) d(LOG_TAG, "loading user items");
615 
616         if (isLaunching && isDesktopLoaded()) {
617             if (DEBUG_LOADERS) d(LOG_TAG, "  --> items loaded, return");
618             if (loadApplications) startApplicationsLoader(launcher, true);
619             // We have already loaded our data from the DB
620             launcher.onDesktopItemsLoaded(mDesktopItems, mDesktopAppWidgets);
621             return;
622         }
623 
624         if (mDesktopItemsLoader != null && mDesktopItemsLoader.isRunning()) {
625             if (DEBUG_LOADERS) d(LOG_TAG, "  --> stopping workspace loader");
626             mDesktopItemsLoader.stop();
627             // Wait for the currently running thread to finish, this can take a little
628             // time but it should be well below the timeout limit
629             try {
630                 mDesktopLoaderThread.join(APPLICATION_NOT_RESPONDING_TIMEOUT);
631             } catch (InterruptedException e) {
632                 // Empty
633             }
634 
635             // If the thread we are interrupting was tasked to load the list of
636             // applications make sure we keep that information in the new loader
637             // spawned below
638             // note: we don't apply this to localeChanged because the thread can
639             // only be stopped *after* the localeChanged handling has occured
640             loadApplications = mDesktopItemsLoader.mLoadApplications;
641         }
642 
643         if (DEBUG_LOADERS) d(LOG_TAG, "  --> starting workspace loader");
644         mDesktopItemsLoaded = false;
645         mDesktopItemsLoader = new DesktopItemsLoader(launcher, localeChanged, loadApplications,
646                 isLaunching);
647         mDesktopLoaderThread = new Thread(mDesktopItemsLoader, "Desktop Items Loader");
648         mDesktopLoaderThread.start();
649     }
650 
651     private static void updateShortcutLabels(ContentResolver resolver, PackageManager manager) {
652         final Cursor c = resolver.query(LauncherSettings.Favorites.CONTENT_URI,
653                 new String[] { LauncherSettings.Favorites._ID, LauncherSettings.Favorites.TITLE,
654                         LauncherSettings.Favorites.INTENT, LauncherSettings.Favorites.ITEM_TYPE },
655                 null, null, null);
656 
657         final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
658         final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
659         final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
660         final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
661 
662         // boolean changed = false;
663 
664         try {
665             while (c.moveToNext()) {
666                 try {
667                     if (c.getInt(itemTypeIndex) !=
668                             LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
669                         continue;
670                     }
671 
672                     final String intentUri = c.getString(intentIndex);
673                     if (intentUri != null) {
674                         final Intent shortcut = Intent.parseUri(intentUri, 0);
675                         if (Intent.ACTION_MAIN.equals(shortcut.getAction())) {
676                             final ComponentName name = shortcut.getComponent();
677                             if (name != null) {
678                                 final ActivityInfo activityInfo = manager.getActivityInfo(name, 0);
679                                 final String title = c.getString(titleIndex);
680                                 String label = getLabel(manager, activityInfo);
681 
682                                 if (title == null || !title.equals(label)) {
683                                     final ContentValues values = new ContentValues();
684                                     values.put(LauncherSettings.Favorites.TITLE, label);
685 
686                                     resolver.update(
687                                             LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION,
688                                             values, "_id=?",
689                                             new String[] { String.valueOf(c.getLong(idIndex)) });
690 
691                                     // changed = true;
692                                 }
693                             }
694                         }
695                     }
696                 } catch (URISyntaxException e) {
697                     // Ignore
698                 } catch (PackageManager.NameNotFoundException e) {
699                     // Ignore
700                 }
701             }
702         } finally {
703             c.close();
704         }
705 
706         // if (changed) resolver.notifyChange(Settings.Favorites.CONTENT_URI, null);
707     }
708 
709     private static String getLabel(PackageManager manager, ActivityInfo activityInfo) {
710         String label = activityInfo.loadLabel(manager).toString();
711         if (label == null) {
712             label = manager.getApplicationLabel(activityInfo.applicationInfo).toString();
713             if (label == null) {
714                 label = activityInfo.name;
715             }
716         }
717         return label;
718     }
719 
720     private class DesktopItemsLoader implements Runnable {
721         private volatile boolean mStopped;
722         private volatile boolean mRunning;
723 
724         private final WeakReference<Launcher> mLauncher;
725         private final boolean mLocaleChanged;
726         private final boolean mLoadApplications;
727         private final boolean mIsLaunching;
728         private final int mId;        
729 
730         DesktopItemsLoader(Launcher launcher, boolean localeChanged, boolean loadApplications,
731                 boolean isLaunching) {
732             mLoadApplications = loadApplications;
733             mIsLaunching = isLaunching;
734             mLauncher = new WeakReference<Launcher>(launcher);
735             mLocaleChanged = localeChanged;
736             mId = sWorkspaceLoaderCount.getAndIncrement();
737         }
738 
739         void stop() {
740             mStopped = true;
741         }
742 
743         boolean isRunning() {
744             return mRunning;
745         }
746 
747         public void run() {
748             if (DEBUG_LOADERS) d(LOG_TAG, "  ----> running workspace loader (" + mId + ")");
749 
750             mRunning = true;
751 
752             android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
753 
754             final Launcher launcher = mLauncher.get();
755             final ContentResolver contentResolver = launcher.getContentResolver();
756             final PackageManager manager = launcher.getPackageManager();
757 
758             if (mLocaleChanged) {
759                 updateShortcutLabels(contentResolver, manager);
760             }
761 
762             mDesktopItems = new ArrayList<ItemInfo>();
763             mDesktopAppWidgets = new ArrayList<LauncherAppWidgetInfo>();
764             mFolders = new HashMap<Long, FolderInfo>();
765 
766             final ArrayList<ItemInfo> desktopItems = mDesktopItems;
767             final ArrayList<LauncherAppWidgetInfo> desktopAppWidgets = mDesktopAppWidgets;
768 
769             final Cursor c = contentResolver.query(
770                     LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
771 
772             try {
773                 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
774                 final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
775                 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
776                 final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
777                 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
778                 final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
779                 final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
780                 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
781                 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
782                 final int appWidgetIdIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.APPWIDGET_ID);
783                 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
784                 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
785                 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
786                 final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
787                 final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);
788                 final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
789                 final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
790 
791                 ApplicationInfo info;
792                 String intentDescription;
793                 Widget widgetInfo;
794                 LauncherAppWidgetInfo appWidgetInfo;
795                 int container;
796                 long id;
797                 Intent intent;
798 
799                 final HashMap<Long, FolderInfo> folders = mFolders;
800 
801                 while (!mStopped && c.moveToNext()) {
802                     try {
803                         int itemType = c.getInt(itemTypeIndex);
804 
805                         switch (itemType) {
806                         case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
807                         case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
808                             intentDescription = c.getString(intentIndex);
809                             try {
810                                 intent = Intent.parseUri(intentDescription, 0);
811                             } catch (java.net.URISyntaxException e) {
812                                 continue;
813                             }
814 
815                             if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
816                                 info = getApplicationInfo(manager, intent, launcher);
817                             } else {
818                                 info = getApplicationInfoShortcut(c, launcher, iconTypeIndex,
819                                         iconPackageIndex, iconResourceIndex, iconIndex);
820                             }
821 
822                             if (info == null) {
823                                 info = new ApplicationInfo();
824                                 info.icon = manager.getDefaultActivityIcon();
825                             }
826 
827                             if (info != null) {
828                                 info.title = c.getString(titleIndex);
829                                 info.intent = intent;
830 
831                                 info.id = c.getLong(idIndex);
832                                 container = c.getInt(containerIndex);
833                                 info.container = container;
834                                 info.screen = c.getInt(screenIndex);
835                                 info.cellX = c.getInt(cellXIndex);
836                                 info.cellY = c.getInt(cellYIndex);
837 
838                                 switch (container) {
839                                 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
840                                     desktopItems.add(info);
841                                     break;
842                                 default:
843                                     // Item is in a user folder
844                                     UserFolderInfo folderInfo =
845                                             findOrMakeUserFolder(folders, container);
846                                     folderInfo.add(info);
847                                     break;
848                                 }
849                             }
850                             break;
851                         case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
852 
853                             id = c.getLong(idIndex);
854                             UserFolderInfo folderInfo = findOrMakeUserFolder(folders, id);
855 
856                             folderInfo.title = c.getString(titleIndex);
857 
858                             folderInfo.id = id;
859                             container = c.getInt(containerIndex);
860                             folderInfo.container = container;
861                             folderInfo.screen = c.getInt(screenIndex);
862                             folderInfo.cellX = c.getInt(cellXIndex);
863                             folderInfo.cellY = c.getInt(cellYIndex);
864 
865                             switch (container) {
866                                 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
867                                     desktopItems.add(folderInfo);
868                                     break;
869                             }
870                             break;
871                         case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER:
872 
873                             id = c.getLong(idIndex);
874                             LiveFolderInfo liveFolderInfo = findOrMakeLiveFolder(folders, id);
875 
876                             intentDescription = c.getString(intentIndex);
877                             intent = null;
878                             if (intentDescription != null) {
879                                 try {
880                                     intent = Intent.parseUri(intentDescription, 0);
881                                 } catch (java.net.URISyntaxException e) {
882                                     // Ignore, a live folder might not have a base intent
883                                 }
884                             }
885 
886                             liveFolderInfo.title = c.getString(titleIndex);
887                             liveFolderInfo.id = id;
888                             container = c.getInt(containerIndex);
889                             liveFolderInfo.container = container;
890                             liveFolderInfo.screen = c.getInt(screenIndex);
891                             liveFolderInfo.cellX = c.getInt(cellXIndex);
892                             liveFolderInfo.cellY = c.getInt(cellYIndex);
893                             liveFolderInfo.uri = Uri.parse(c.getString(uriIndex));
894                             liveFolderInfo.baseIntent = intent;
895                             liveFolderInfo.displayMode = c.getInt(displayModeIndex);
896 
897                             loadLiveFolderIcon(launcher, c, iconTypeIndex, iconPackageIndex,
898                                     iconResourceIndex, liveFolderInfo);
899 
900                             switch (container) {
901                                 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
902                                     desktopItems.add(liveFolderInfo);
903                                     break;
904                             }
905                             break;
906                         case LauncherSettings.Favorites.ITEM_TYPE_WIDGET_SEARCH:
907                             widgetInfo = Widget.makeSearch();
908 
909                             container = c.getInt(containerIndex);
910                             if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
911                                 e(Launcher.LOG_TAG, "Widget found where container "
912                                         + "!= CONTAINER_DESKTOP  ignoring!");
913                                 continue;
914                             }
915 
916                             widgetInfo.id = c.getLong(idIndex);
917                             widgetInfo.screen = c.getInt(screenIndex);
918                             widgetInfo.container = container;
919                             widgetInfo.cellX = c.getInt(cellXIndex);
920                             widgetInfo.cellY = c.getInt(cellYIndex);
921 
922                             desktopItems.add(widgetInfo);
923                             break;
924                         case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
925                             // Read all Launcher-specific widget details
926                             int appWidgetId = c.getInt(appWidgetIdIndex);
927                             appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId);
928                             appWidgetInfo.id = c.getLong(idIndex);
929                             appWidgetInfo.screen = c.getInt(screenIndex);
930                             appWidgetInfo.cellX = c.getInt(cellXIndex);
931                             appWidgetInfo.cellY = c.getInt(cellYIndex);
932                             appWidgetInfo.spanX = c.getInt(spanXIndex);
933                             appWidgetInfo.spanY = c.getInt(spanYIndex);
934 
935                             container = c.getInt(containerIndex);
936                             if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
937                                 e(Launcher.LOG_TAG, "Widget found where container "
938                                         + "!= CONTAINER_DESKTOP -- ignoring!");
939                                 continue;
940                             }
941                             appWidgetInfo.container = c.getInt(containerIndex);
942 
943                             desktopAppWidgets.add(appWidgetInfo);
944                             break;
945                         }
946                     } catch (Exception e) {
947                         w(Launcher.LOG_TAG, "Desktop items loading interrupted:", e);
948                     }
949                 }
950             } finally {
951                 c.close();
952             }
953 
954             if (!mStopped) {
955                 if (DEBUG_LOADERS)  {
956                     d(LOG_TAG, "  --> done loading workspace");
957                     d(LOG_TAG, "  ----> worskpace items=" + desktopItems.size());                
958                     d(LOG_TAG, "  ----> worskpace widgets=" + desktopAppWidgets.size());
959                 }
960 
961                 // Create a copy of the lists in case the workspace loader is restarted
962                 // and the list are cleared before the UI can go through them
963                 final ArrayList<ItemInfo> uiDesktopItems =
964                         new ArrayList<ItemInfo>(desktopItems);
965                 final ArrayList<LauncherAppWidgetInfo> uiDesktopWidgets =
966                         new ArrayList<LauncherAppWidgetInfo>(desktopAppWidgets);
967 
968                 if (!mStopped) {
969                     d(LOG_TAG, "  ----> items cloned, ready to refresh UI");                
970                     launcher.runOnUiThread(new Runnable() {
971                         public void run() {
972                             if (DEBUG_LOADERS) d(LOG_TAG, "  ----> onDesktopItemsLoaded()");
973                             launcher.onDesktopItemsLoaded(uiDesktopItems, uiDesktopWidgets);
974                         }
975                     });
976                 }
977 
978                 if (mLoadApplications) {
979                     if (DEBUG_LOADERS) {
980                         d(LOG_TAG, "  ----> loading applications from workspace loader");
981                     }
982                     startApplicationsLoader(launcher, mIsLaunching);
983                 }
984 
985                 mDesktopItemsLoaded = true;
986             } else {
987                 if (DEBUG_LOADERS) d(LOG_TAG, "  ----> worskpace loader was stopped");
988             }
989             mRunning = false;
990         }
991     }
992 
993     private static void loadLiveFolderIcon(Launcher launcher, Cursor c, int iconTypeIndex,
994             int iconPackageIndex, int iconResourceIndex, LiveFolderInfo liveFolderInfo) {
995 
996         int iconType = c.getInt(iconTypeIndex);
997         switch (iconType) {
998             case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
999                 String packageName = c.getString(iconPackageIndex);
1000                 String resourceName = c.getString(iconResourceIndex);
1001                 PackageManager packageManager = launcher.getPackageManager();
1002                 try {
1003                     Resources resources = packageManager.getResourcesForApplication(packageName);
1004                     final int id = resources.getIdentifier(resourceName, null, null);
1005                     liveFolderInfo.icon = resources.getDrawable(id);
1006                 } catch (Exception e) {
1007                     liveFolderInfo.icon =
1008                             launcher.getResources().getDrawable(R.drawable.ic_launcher_folder);
1009                 }
1010                 liveFolderInfo.iconResource = new Intent.ShortcutIconResource();
1011                 liveFolderInfo.iconResource.packageName = packageName;
1012                 liveFolderInfo.iconResource.resourceName = resourceName;
1013                 break;
1014             default:
1015                 liveFolderInfo.icon =
1016                         launcher.getResources().getDrawable(R.drawable.ic_launcher_folder);
1017         }
1018     }
1019 
1020     /**
1021      * Finds the user folder defined by the specified id.
1022      *
1023      * @param id The id of the folder to look for.
1024      *
1025      * @return A UserFolderInfo if the folder exists or null otherwise.
1026      */
1027     FolderInfo findFolderById(long id) {
1028         return mFolders.get(id);
1029     }
1030 
1031     void addFolder(FolderInfo info) {
1032         mFolders.put(info.id, info);
1033     }
1034 
1035     /**
1036      * Return an existing UserFolderInfo object if we have encountered this ID previously, or make a
1037      * new one.
1038      */
1039     private UserFolderInfo findOrMakeUserFolder(HashMap<Long, FolderInfo> folders, long id) {
1040         // See if a placeholder was created for us already
1041         FolderInfo folderInfo = folders.get(id);
1042         if (folderInfo == null || !(folderInfo instanceof UserFolderInfo)) {
1043             // No placeholder -- create a new instance
1044             folderInfo = new UserFolderInfo();
1045             folders.put(id, folderInfo);
1046         }
1047         return (UserFolderInfo) folderInfo;
1048     }
1049 
1050     /**
1051      * Return an existing UserFolderInfo object if we have encountered this ID previously, or make a
1052      * new one.
1053      */
1054     private LiveFolderInfo findOrMakeLiveFolder(HashMap<Long, FolderInfo> folders, long id) {
1055         // See if a placeholder was created for us already
1056         FolderInfo folderInfo = folders.get(id);
1057         if (folderInfo == null || !(folderInfo instanceof LiveFolderInfo)) {
1058             // No placeholder -- create a new instance
1059             folderInfo = new LiveFolderInfo();
1060             folders.put(id, folderInfo);
1061         }
1062         return (LiveFolderInfo) folderInfo;
1063     }
1064 
1065     /**
1066      * Remove the callback for the cached drawables or we leak the previous
1067      * Home screen on orientation change.
1068      */
1069     void unbind() {
1070         // Interrupt the applications loader before setting the adapter to null
1071         stopAndWaitForApplicationsLoader();
1072         mApplicationsAdapter = null;
1073         unbindAppDrawables(mApplications);
1074         unbindDrawables(mDesktopItems);
1075         unbindAppWidgetHostViews(mDesktopAppWidgets);
1076         unbindCachedIconDrawables();
1077     }
1078 
1079     /**
1080      * Remove the callback for the cached drawables or we leak the previous
1081      * Home screen on orientation change.
1082      */
1083     private void unbindDrawables(ArrayList<ItemInfo> desktopItems) {
1084         if (desktopItems != null) {
1085             final int count = desktopItems.size();
1086             for (int i = 0; i < count; i++) {
1087                 ItemInfo item = desktopItems.get(i);
1088                 switch (item.itemType) {
1089                 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1090                 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1091                     ((ApplicationInfo)item).icon.setCallback(null);
1092                     break;
1093                 }
1094             }
1095         }
1096     }
1097 
1098     /**
1099      * Remove the callback for the cached drawables or we leak the previous
1100      * Home screen on orientation change.
1101      */
1102     private void unbindAppDrawables(ArrayList<ApplicationInfo> applications) {
1103         if (applications != null) {
1104             final int count = applications.size();
1105             for (int i = 0; i < count; i++) {
1106                 applications.get(i).icon.setCallback(null);
1107             }
1108         }
1109     }
1110 
1111     /**
1112      * Remove any {@link LauncherAppWidgetHostView} references in our widgets.
1113      */
1114     private void unbindAppWidgetHostViews(ArrayList<LauncherAppWidgetInfo> appWidgets) {
1115         if (appWidgets != null) {
1116             final int count = appWidgets.size();
1117             for (int i = 0; i < count; i++) {
1118                 LauncherAppWidgetInfo launcherInfo = appWidgets.get(i);
1119                 launcherInfo.hostView = null;
1120             }
1121         }
1122     }
1123 
1124     /**
1125      * Remove the callback for the cached drawables or we leak the previous
1126      * Home screen on orientation change.
1127      */
1128     private void unbindCachedIconDrawables() {
1129         for (ApplicationInfo appInfo : mAppInfoCache.values()) {
1130             appInfo.icon.setCallback(null);
1131         }
1132     }
1133 
1134     /**
1135      * Fills in the occupied structure with all of the shortcuts, apps, folders and widgets in
1136      * the model. 
1137      */
1138     void findAllOccupiedCells(boolean[][] occupied, int countX, int countY, int screen) {
1139         final ArrayList<ItemInfo> desktopItems = mDesktopItems;
1140         if (desktopItems != null) {
1141             final int count = desktopItems.size();
1142             for (int i = 0; i < count; i++) {
1143                 ItemInfo item = desktopItems.get(i);
1144                 addOccupiedCells(occupied, screen, desktopItems.get(i));
1145             }
1146         }
1147         
1148         final ArrayList<LauncherAppWidgetInfo> desktopAppWidgets = mDesktopAppWidgets;
1149         if (desktopAppWidgets != null) {
1150             final int count = desktopAppWidgets.size();
1151             for (int i = 0; i < count; i++) {
1152                 addOccupiedCells(occupied, screen, desktopAppWidgets.get(i));
1153             }
1154         }
1155     }
1156 
1157     /**
1158      * Add the footprint of the specified item to the occupied array
1159      */
1160     private void addOccupiedCells(boolean[][] occupied, int screen,
1161             ItemInfo item) {
1162         if (item.screen == screen) {
1163             for (int xx = item.cellX; xx < item.cellX + item.spanX; xx++) {
1164                 for (int yy = item.cellY; yy < item.cellY + item.spanY; yy++) {
1165                     occupied[xx][yy] = true;
1166                 }
1167             }
1168         }
1169     }
1170     
1171     /**
1172      * @return The current list of applications
1173      */
1174     ApplicationsAdapter getApplicationsAdapter() {
1175         return mApplicationsAdapter;
1176     }
1177 
1178     /**
1179      * Add an item to the desktop
1180      * @param info
1181      */
1182     void addDesktopItem(ItemInfo info) {
1183         // TODO: write to DB; also check that folder has been added to folders list
1184         mDesktopItems.add(info);
1185     }
1186 
1187     /**
1188      * Remove an item from the desktop
1189      * @param info
1190      */
1191     void removeDesktopItem(ItemInfo info) {
1192         // TODO: write to DB; figure out if we should remove folder from folders list
1193         mDesktopItems.remove(info);
1194     }
1195 
1196     /**
1197      * Add a widget to the desktop
1198      */
1199     void addDesktopAppWidget(LauncherAppWidgetInfo info) {
1200         mDesktopAppWidgets.add(info);
1201     }
1202 
1203     /**
1204      * Remove a widget from the desktop
1205      */
1206     void removeDesktopAppWidget(LauncherAppWidgetInfo info) {
1207         mDesktopAppWidgets.remove(info);
1208     }
1209 
1210     /**
1211      * Make an ApplicationInfo object for an application
1212      */
1213     private static ApplicationInfo getApplicationInfo(PackageManager manager, Intent intent,
1214                                                       Context context) {
1215         final ResolveInfo resolveInfo = manager.resolveActivity(intent, 0);
1216 
1217         if (resolveInfo == null) {
1218             return null;
1219         }
1220 
1221         final ApplicationInfo info = new ApplicationInfo();
1222         final ActivityInfo activityInfo = resolveInfo.activityInfo;
1223         info.icon = Utilities.createIconThumbnail(activityInfo.loadIcon(manager), context);
1224         if (info.title == null || info.title.length() == 0) {
1225             info.title = activityInfo.loadLabel(manager);
1226         }
1227         if (info.title == null) {
1228             info.title = "";
1229         }
1230         info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
1231         return info;
1232     }
1233 
1234     /**
1235      * Make an ApplicationInfo object for a sortcut
1236      */
1237     private ApplicationInfo getApplicationInfoShortcut(Cursor c, Context context,
1238             int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex) {
1239 
1240         final ApplicationInfo info = new ApplicationInfo();
1241         info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
1242 
1243         int iconType = c.getInt(iconTypeIndex);
1244         switch (iconType) {
1245             case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
1246                 String packageName = c.getString(iconPackageIndex);
1247                 String resourceName = c.getString(iconResourceIndex);
1248                 PackageManager packageManager = context.getPackageManager();
1249                 try {
1250                     Resources resources = packageManager.getResourcesForApplication(packageName);
1251                     final int id = resources.getIdentifier(resourceName, null, null);
1252                     info.icon = Utilities.createIconThumbnail(resources.getDrawable(id), context);
1253                 } catch (Exception e) {
1254                     info.icon = packageManager.getDefaultActivityIcon();
1255                 }
1256                 info.iconResource = new Intent.ShortcutIconResource();
1257                 info.iconResource.packageName = packageName;
1258                 info.iconResource.resourceName = resourceName;
1259                 info.customIcon = false;
1260                 break;
1261             case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
1262                 byte[] data = c.getBlob(iconIndex);
1263                 try {
1264                     Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
1265                     info.icon = new FastBitmapDrawable(
1266                             Utilities.createBitmapThumbnail(bitmap, context));
1267                 } catch (Exception e) {
1268                     packageManager = context.getPackageManager();
1269                     info.icon = packageManager.getDefaultActivityIcon();
1270                 }
1271                 info.filtered = true;
1272                 info.customIcon = true;
1273                 break;
1274             default:
1275                 info.icon = context.getPackageManager().getDefaultActivityIcon();
1276                 info.customIcon = false;
1277                 break;
1278         }
1279         return info;
1280     }
1281 
1282     /**
1283      * Remove an item from the in-memory represention of a user folder. Does not change the DB.
1284      */
1285     void removeUserFolderItem(UserFolderInfo folder, ItemInfo info) {
1286         //noinspection SuspiciousMethodCalls
1287         folder.contents.remove(info);
1288     }
1289 
1290     /**
1291      * Removes a UserFolder from the in-memory list of folders. Does not change the DB.
1292      * @param userFolderInfo
1293      */
1294     void removeUserFolder(UserFolderInfo userFolderInfo) {
1295         mFolders.remove(userFolderInfo.id);
1296     }
1297 
1298     /**
1299      * Adds an item to the DB if it was not created previously, or move it to a new
1300      * <container, screen, cellX, cellY>
1301      */
1302     static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
1303             int screen, int cellX, int cellY) {
1304         if (item.container == ItemInfo.NO_ID) {
1305             // From all apps
1306             addItemToDatabase(context, item, container, screen, cellX, cellY, false);
1307         } else {
1308             // From somewhere else
1309             moveItemInDatabase(context, item, container, screen, cellX, cellY);
1310         }
1311     }
1312 
1313     /**
1314      * Move an item in the DB to a new <container, screen, cellX, cellY>
1315      */
1316     static void moveItemInDatabase(Context context, ItemInfo item, long container, int screen,
1317             int cellX, int cellY) {
1318         item.container = container;
1319         item.screen = screen;
1320         item.cellX = cellX;
1321         item.cellY = cellY;
1322 
1323         final ContentValues values = new ContentValues();
1324         final ContentResolver cr = context.getContentResolver();
1325 
1326         values.put(LauncherSettings.Favorites.CONTAINER, item.container);
1327         values.put(LauncherSettings.Favorites.CELLX, item.cellX);
1328         values.put(LauncherSettings.Favorites.CELLY, item.cellY);
1329         values.put(LauncherSettings.Favorites.SCREEN, item.screen);
1330 
1331         cr.update(LauncherSettings.Favorites.getContentUri(item.id, false), values, null, null);
1332     }
1333 
1334     /**
1335      * Returns true if the shortcuts already exists in the database.
1336      * we identify a shortcut by its title and intent.
1337      */
1338     static boolean shortcutExists(Context context, String title, Intent intent) {
1339         final ContentResolver cr = context.getContentResolver();
1340         Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
1341             new String[] { "title", "intent" }, "title=? and intent=?",
1342             new String[] { title, intent.toUri(0) }, null);
1343         boolean result = false;
1344         try {
1345             result = c.moveToFirst();
1346         } finally {
1347             c.close();
1348         }
1349         return result;
1350     }
1351 
1352     FolderInfo getFolderById(Context context, long id) {
1353         final ContentResolver cr = context.getContentResolver();
1354         Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null,
1355                 "_id=? and (itemType=? or itemType=?)",
1356                 new String[] { String.valueOf(id),
1357                         String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER),
1358                         String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER) }, null);
1359 
1360         try {
1361             if (c.moveToFirst()) {
1362                 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
1363                 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
1364                 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
1365                 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
1366                 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
1367                 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
1368 
1369                 FolderInfo folderInfo = null;
1370                 switch (c.getInt(itemTypeIndex)) {
1371                     case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
1372                         folderInfo = findOrMakeUserFolder(mFolders, id);
1373                         break;
1374                     case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER:
1375                         folderInfo = findOrMakeLiveFolder(mFolders, id);
1376                         break;
1377                 }
1378 
1379                 folderInfo.title = c.getString(titleIndex);
1380                 folderInfo.id = id;
1381                 folderInfo.container = c.getInt(containerIndex);
1382                 folderInfo.screen = c.getInt(screenIndex);
1383                 folderInfo.cellX = c.getInt(cellXIndex);
1384                 folderInfo.cellY = c.getInt(cellYIndex);
1385 
1386                 return folderInfo;
1387             }
1388         } finally {
1389             c.close();
1390         }
1391 
1392         return null;
1393     }
1394 
1395     /**
1396      * Add an item to the database in a specified container. Sets the container, screen, cellX and
1397      * cellY fields of the item. Also assigns an ID to the item.
1398      */
1399     static void addItemToDatabase(Context context, ItemInfo item, long container,
1400             int screen, int cellX, int cellY, boolean notify) {
1401         item.container = container;
1402         item.screen = screen;
1403         item.cellX = cellX;
1404         item.cellY = cellY;
1405 
1406         final ContentValues values = new ContentValues();
1407         final ContentResolver cr = context.getContentResolver();
1408 
1409         item.onAddToDatabase(values);
1410 
1411         Uri result = cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI :
1412                 LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values);
1413 
1414         if (result != null) {
1415             item.id = Integer.parseInt(result.getPathSegments().get(1));
1416         }
1417     }
1418 
1419     /**
1420      * Update an item to the database in a specified container.
1421      */
1422     static void updateItemInDatabase(Context context, ItemInfo item) {
1423         final ContentValues values = new ContentValues();
1424         final ContentResolver cr = context.getContentResolver();
1425 
1426         item.onAddToDatabase(values);
1427 
1428         cr.update(LauncherSettings.Favorites.getContentUri(item.id, false), values, null, null);
1429     }
1430 
1431     /**
1432      * Removes the specified item from the database
1433      * @param context
1434      * @param item
1435      */
1436     static void deleteItemFromDatabase(Context context, ItemInfo item) {
1437         final ContentResolver cr = context.getContentResolver();
1438 
1439         cr.delete(LauncherSettings.Favorites.getContentUri(item.id, false), null, null);
1440     }
1441 
1442 
1443     /**
1444      * Remove the contents of the specified folder from the database
1445      */
1446     static void deleteUserFolderContentsFromDatabase(Context context, UserFolderInfo info) {
1447         final ContentResolver cr = context.getContentResolver();
1448 
1449         cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null);
1450         cr.delete(LauncherSettings.Favorites.CONTENT_URI,
1451                 LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
1452     }
1453 }